
//Copyright 1997-2009 Syrinx Development, Inc.
//This file is part of the Syrinx Web Application Framework (SWAF).
// == BEGIN LICENSE ==
//
// Licensed under the terms of any of the following licenses at your
// choice:
//
//  - GNU General Public License Version 3 or later (the "GPL")
//    http://www.gnu.org/licenses/gpl.html
//
//  - GNU Lesser General Public License Version 3 or later (the "LGPL")
//    http://www.gnu.org/licenses/lgpl.html
//
//  - Mozilla Public License Version 1.1 or later (the "MPL")
//    http://www.mozilla.org/MPL/MPL-1.1.html
//
// == END LICENSE ==
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Xml;

using Swaf.BizObj;
using Swaf.Container;
using Swaf.Gui;
using Swaf.Gui.AspNet2;
using Swaf.Security;

namespace Syrinx.Gui.AspNet.NitobiCtls
{
	public class ScriptMap : Dictionary<string, string> {}

	[ParseChildren(false)]
	public class NitobiBizObjDisplay : GenericBizObjDisplay, INitobiClientControl
	{
		protected static string s_setupScript = @"
<script>//{0}:
{2}
{1}cc = new ClientControl(document.getElementById('{0}'), setBizObjDetailsState, getBizObjDetailsState);
{1}cc.id = '{0}';
{1}cc.bizProcs = new Array();
{1}cc.pushChildValues = showBizObj{1};
{1}cc.pullChildValues = updateBizObj{1};
{3}BizProc.addClientControl({1}cc);
</script>
";
		protected static string s_globalScript = @"
function setBizObjDetailsState(stateEl)
{
	var html = stateEl.getAttribute('html');
	if(html != null && html != '')
		this.dhtmlObj.innerHTML = html;

	var script = stateEl.getAttribute('script');
	eval(script);
	var newMe = eval(this.id + 'cc');
	newMe.pushChildValues(stateEl.selectSingleNode('BizObj'));
}

function getBizObjDetailsState(stateEl)
{
	var bizobj = stateEl.ownerDocument.createElement('BizObj');
	stateEl.appendChild(bizobj);
	if(this.pullChildValues != null)
		this.pullChildValues(bizobj);
}
";

		protected internal static string s_nitobiComboSetupScript = "{0}var c = nitobi.loadComponent('{1}_cm');if({2})c.GetList().GetPage(0, 0);c.GetList().SetSelectedRowIndex(-1);try{{{1}_cmSetup();}}catch(e){{}}";
		protected static ScriptMap s_controlPushScripts = new ScriptMap();
		protected static ScriptMap s_controlPullScripts = new ScriptMap();
		static NitobiBizObjDisplay()
		{
			s_controlPushScripts.Add("textbox", "nitobi.html.getElement('{0}').value = bizobj.getAttribute('{1}');");
			s_controlPushScripts.Add("label", "nitobi.html.getElement('{0}').innerHTML = bizobj.getAttribute('{1}');");
			s_controlPushScripts.Add("readonlytext", "nitobi.html.getElement('{0}').innerHTML = bizobj.getAttribute('{1}');");
			s_controlPushScripts.Add("hiddenfield", s_controlPushScripts["textbox"]);
			s_controlPushScripts.Add("checkbox", "nitobi.html.getElement('{0}').checked = bizobj.getAttribute('{1}').toLowerCase() == 'true'?true:false;");
			s_controlPushScripts.Add("dropdowncalendar", "nitobi.html.getElement('{0}Fld').value = bizobj.getAttribute('{1}');");
			s_controlPushScripts.Add("nitobicomboboxbizobjlistdisplay", "setSelectedRowByKey(nitobi.getComponent('{0}_cm'),bizobj.getAttribute('{1}'));");
			s_controlPushScripts.Add("freetextbox", "FTB_API['{0}'].SetHtml(bizobj.getAttribute('{1}'))");
			s_controlPushScripts.Add("image", "nitobi.html.getElement('{0}').src = bizobj.getAttribute('{1}');");
			s_controlPushScripts.Add("hyperlink", "nitobi.html.getElement('{0}').href = bizobj.getAttribute('{1}');");
			s_controlPushScripts.Add("dropdownlist", "var dd=nitobi.html.getElement('{0}');for(var p=0;p<dd.options.length;p++)if(dd.options[p].value==bizobj.getAttribute('{1}')){{dd.options[p].selected=true;break;}};");

			s_controlPullScripts.Add("textbox", "bizobj.setAttribute('{1}', nitobi.html.getElement('{0}').value);");
			s_controlPullScripts.Add("hiddenfield", s_controlPullScripts["textbox"]);
			s_controlPullScripts.Add("checkbox", "bizobj.setAttribute('{1}', nitobi.html.getElement('{0}').checked?'True':'False');");
			s_controlPullScripts.Add("dropdowncalendar", "bizobj.setAttribute('{1}', nitobi.html.getElement('{0}Fld').value);");
			s_controlPullScripts.Add("nitobicomboboxbizobjlistdisplay", "var x = getSelectedRowKey(nitobi.getComponent('{0}_cm')); if(x != null)bizobj.setAttribute('{1}', x);");
			s_controlPullScripts.Add("freetextbox", "bizobj.setAttribute('{1}', FTB_API['{0}'].GetHtml());");
			s_controlPullScripts.Add("dropdownlist", "var dd=nitobi.html.getElement('{0}');bizobj.setAttribute('{1}', dd.options[dd.selectedIndex].value);");
		}

		protected HiddenField m_idFld = null;
		protected XmlElement m_ajaxClientUpdates = null;
		protected string m_oldTemplateNames = null;
		protected string m_newTemplateNames = null;
		protected string m_processedFields = null;
		protected List<INitobiClientControl> m_nitobiChildren = new List<INitobiClientControl>();


		public static ScriptMap PushScriptMap { get { return s_controlPushScripts; } }
		public static ScriptMap PullScriptMap { get { return s_controlPullScripts; } }

		
		public NitobiBizObjDisplay(): base(){}
		public NitobiBizObjDisplay(ActiveWidget widget) : base(widget) { }
		
		public override void displayBizObj(IBizObj bizObj)
		{
			base.CurrentBizObj = bizObj;

			if(Visible==false) return;
			calculateTemplateNames();
			if (string.Compare(m_oldTemplateNames, m_newTemplateNames) != 0)
			{
				base.m_contentName = m_newTemplateNames;
				//TODO: Build list of all bizprocs before switching template.
				clearControls();
				createControls();
				//TODO: Build list of all new bizprocs after switching
				//TODO: if a bizproc from the old list is not in the new list,
				//have it stop processing.
			}
			base.displayBizObj(bizObj);
		}

		protected void calculateTemplateNames()
		{
			m_newTemplateNames = ContentName;// base.getTemplateNames();
			if (m_newTemplateNames == null)
			{
				string template = m_editPriv > Privileges.ReadOnly ? EditableTemplate : ReadOnlyTemplate;
				if (template == null)
					template = ReadOnlyTemplate;
				m_newTemplateNames = template;
			}
		}

		//public virtual string ParentBizProcId { get { return m_parentBizProc == null ? "" : m_parentBizProc.Id; } }

		public override string ClientID
		{
			get
			{
				return base.ClientID.Replace('_', '$');
			}
		}
		public override void updateBizObj(IBizObj bizObj)
		{
			CurrentBizObj = bizObj;
			if (Visible == false) return;
			//calculateTemplateNames();
			//if (string.Compare(m_oldTemplateNames, m_newTemplateNames) != 0)
			//{
			//    base.m_contentName = m_newTemplateNames;
			//    //TODO: Build list of all bizprocs before switching template.
			//    clearControls();
			//    createControls();
			//    //TODO: Build list of all new bizprocs after switching
			//    //TODO: if a bizproc from the old list is not in the new list,
			//    //have it stop processing.
			//}

			//base.updateBizObj(bizObj);
			if (m_ajaxClientUpdates != null && bizObj != null)
			{
				string keyFieldName = bizObj.type.KeyFieldNames;
				foreach (XmlAttribute a in m_ajaxClientUpdates.Attributes)
				{
					string fieldName = a.Name;
					string fieldVal = a.Value;
					if ((bizObj.get(fieldName) == null && fieldVal != "") || (bizObj.get(fieldName) != null && fieldName.CompareTo(keyFieldName) != 0 && fieldVal.CompareTo(bizObj.get(fieldName).ToString()) != 0))
						bizObj.put(fieldName, fieldVal);
				}
			}
			else
				base.updateBizObj(bizObj);
		}

		protected override void AddParsedSubObject(object obj)
		{
			base.AddParsedSubObject(obj);
			
			//System.Web.UI.LiteralControl control = (System.Web.UI.LiteralControl)obj;
			//if (control.Text.Trim().Length != 0)
			//{
			//    XmlDocument doc = new XmlDocument();
			//    doc.LoadXml(control.Text);
			//    base.m_localMacroValues = new FlexiMap(doc.DocumentElement);
			//}
		}

		protected override void OnPreRender(EventArgs e)
		{
			base.OnPreRender(e);
			if (Visible == false) return;

			if (base.CurrentBizObj != null)
			{
				m_idFld = new HiddenField();
				m_idFld.ID = base.CurrentBizObj.type.KeyFieldNames;
				Controls.Add(m_idFld);
			}

			ActiveBizProc bizproc = BPC.getActiveBizProc(State.ParentBizProcIds[0]);
			Page.ClientScript.RegisterClientScriptBlock(typeof(NitobiBizObjDisplay), "GlobalBizObj", s_globalScript, true);

			//Page.ClientScript.RegisterStartupScript(typeof(NitobiBizObjDisplay), "BizObj" + ID, generateBrowserClientScript(false), true);

			//if (m_parentBizProc != null)
			//{
			//    StringBuilder buff = new StringBuilder();
			//    string controlScript = null;
			//    BizProcInfo mgr = BizProcInfo.createInstance(m_parentBizProc, false);
			//    ControlInfo ctlInfo = findControlInfo(mgr);
			//    ctlInfo.ChildNames = new List<string>();
			//    foreach (Control ctl in Controls)
			//        if (!getControlShowBizObjScript(ctl, buff) && s_controlPushScripts.TryGetValue(ctl.GetType().Name.ToLower(), out controlScript))
			//            ctlInfo.ChildNames.Add((ctl is NitobiComboBoxBizObjListDisplay) ? ctl.UniqueID : ctl.ClientID);
			//}
		}

		/*
		protected ControlInfo findControlInfo(BizProcInfo mgr)
		{
			//add check on null param
			if(mgr!=null)
				foreach (ControlInfo ctlInfo in mgr.Controls.Values)
					if (ctlInfo.Id == WidgetId)
						return ctlInfo;
			return null;
		}
	*/
		protected void setupPushScript1(Control parent, StringBuilder buff)
		{
			string fieldName = null;
			foreach (Control ctl in parent.Controls)
			{
				if (CurrentBizObj != null && !CurrentBizObj.containsField(fieldName = ctl.ID.Replace('_', '.')))
					fieldName = "";

				string name = ctl.GetType().Name.ToLower();
				if (s_controlPushScripts.ContainsKey(name))
					buff.Append(fieldName).Append("~").Append(name).Append("|");
				setupPushScript1(ctl, buff);
			}
		}

		protected void setupBizProcs1(Control parent, StringBuilder buff)
		{
			foreach (Control ctl in parent.Controls)
				if (ctl is BizProc)
				{
					BizProc bizProcCtl = (BizProc)ctl;
					buff.AppendFormat("cc.bizProcs.push({0}BizProc);\n", bizProcCtl.TheBizProc.Id);
				}
				else
					setupBizProcs1(ctl, buff);
		}

		protected override void Render(HtmlTextWriter writer)
		{
			createControls();
			bool usingAjax = (Page is INitobiPage || (Page is NitobiBasePage && ((NitobiBasePage)Page).UsingAjax));
			if (usingAjax)
			{
				writer.Write("<!--{0}-->", ClientID);

				bool writeBuff = false;
				StringBuilder buff = new StringBuilder(512);
				buff.Append("<script>");
				foreach (Control ctl in Controls)
					if (ctl is BizProc)
					{
						buff.AppendFormat("{0}BizProc = new ClientBizProc('{0}');\n", ctl.ID);
						writeBuff = true;
					}

				buff.Append("</script>");
				if (writeBuff)
					writer.Write(buff);
			}

			base.Render(writer);

			if (usingAjax)
			{
				if (m_idFld != null)
					m_idFld.RenderControl(writer);
				if (m_newTemplateNames == null)
					calculateTemplateNames();

				ActiveBizProc bizproc = BPC.getActiveBizProc(State.ParentBizProcIds[0]);
				writer.Write(s_setupScript, WidgetId, ClientID, generatePushPullClientScriptMethods(false), bizproc.BizProc.Id);
				writer.Write("<!--{0}-->", ClientID);
			}
		}

		protected override void OnUnload(EventArgs e)
		{
			if(BPC != null && State != null)
				State.State.put("previousTemplateName", m_newTemplateNames);
			base.OnUnload(e);
		}
		protected string getAttrName(string id)
		{
			if (id.StartsWith("_"))
				id = id.Substring(1);
			return id.Replace('_', '.');
		}
		protected void setupPushScript2(Control parent, StringBuilder buff)
		{
			string controlScript = null;
			foreach (Control ctl in parent.Controls)
			{
				//Controls that have id's that start with _ are not data fields and are exposed to having their
				//data localized through the static content localization mechanism.  Only have a control field
				//start with _ if it is for static content.
				if (ctl != null && ctl.ID != null && ctl.ID.Length != 0 && ctl.ID[0] != '_')
				{
					if (s_controlPushScripts.TryGetValue(ctl.GetType().Name.ToLower(), out controlScript))
						buff.Append(string.Format(controlScript, ctl.ClientID, getAttrName(ctl.ID), ctl.UniqueID, calcXPath(ctl.ID), ctl.ID)).Append("\n");

					if (!(ctl is INitobiClientControl))
						setupPushScript2(ctl, buff);
				}
			}
		}

		protected void setupPullScript(Control parent, StringBuilder buff)
		{
			string controlScript = null;
			string fieldName = null;
			foreach (Control ctl in parent.Controls)
			{
				//Controls that have id's that start with _ are not data fields and are exposed to having their
				//data localized through the static content localization mechanism.  Only have a control field
				//start with _ if it is for static content.
				if (ctl != null && ctl.ID != null && ctl.ID.Length != 0 && ctl.ID[0] != '_')
				{
					
					if (CurrentBizObj != null && CurrentBizObj.containsField(fieldName = getAttrName(ctl.ID)) && s_controlPullScripts.TryGetValue(ctl.GetType().Name.ToLower(), out controlScript))
						buff.Append(string.Format(controlScript, ctl.ClientID, fieldName, ctl.UniqueID, calcXPath(ctl.ID))).Append("\n");
					if (!(ctl is INitobiClientControl))
						setupPullScript(ctl, buff);
				}
			}
		}

		protected void setupBizProc2(Control parent, StringBuilder buff)
		{
			foreach (Control ctl in parent.Controls)
				if (ctl is BizProc)
				{
					BizProc bizProcCtl = (BizProc)ctl;
					buff.AppendFormat("this.bizProcs.push({0}BizProc);\n", bizProcCtl.TheBizProc.Id);
				}
				else
					setupBizProc2(ctl, buff);
		}

		protected string generatePushPullClientScriptMethods(bool includeObjAssignment)
		{
			base.createControls();
			StringBuilder buff = new StringBuilder(1024);
			
			//Generate pushChildValues javascript method for browser to properly show a bizobj via ajax call
			if (includeObjAssignment)
				buff.Append("this.pushChildValues = function(bizobj){try{\n");
			else
				buff.Append("showBizObj").Append(ClientID).Append(" = function(bizobj){try{\n");
			setupPushScript2(this, buff);
			buff.Append("for(var pos = 0; pos < this.bizProcs.length; pos++)\n	{this.bizProcs[pos].processControlStates(bizobj.ownerDocument);}\n");
			buff.Append("}catch(e){alert('err1:'+e);}}\n");

			//Generate pullChildValues javascript method for browser to properly gather user input from display for ajax call
			if (includeObjAssignment)
				buff.Append("this.pullChildValues = function(bizobj){try{\n");
			else
				buff.Append("updateBizObj").Append(ClientID).Append(" = function(bizobj){try{\n");
			//buff.Append("function updateBizObj").Append(ClientID).Append("(bizobj){try{debugger;\n");
			setupPullScript(this, buff);
			buff.Append("}catch(e){alert('err2:'+e);}}\n");

			return buff.ToString();
		}

		protected string calcXPath(string ctlId)
		{
			string xpathInfo = "";
			int pos = ctlId.LastIndexOf('_');
			if (pos == -1)
				xpathInfo = "//@" + ctlId;
			else
				xpathInfo = "//" + ctlId.Substring(0, pos) + "/@" + ctlId.Substring(pos + 1);

			return xpathInfo;
		}

		#region INitobiClientControl Members
		public bool CreateOnLoad { set { m_createActiveOnLoad = value; } }

		public bool NeedToGenerateHtmlWithServerControls 
		{
			get 
			{
				string previousTemplateName = State.State.get("previousTemplateName") as string;
				bool shouldIt = string.Compare(m_newTemplateNames, previousTemplateName, true) != 0;
				if (!shouldIt)
					foreach (INitobiClientControl child in m_nitobiChildren)
						if (shouldIt = child.NeedToGenerateHtmlWithServerControls)
							break;
				return shouldIt;
			}
		}

		public void setState(XmlElement clientState)
		{
			m_processedFields = clientState.GetAttribute("fields");
			m_ajaxClientUpdates = clientState.SelectSingleNode("BizObj") as XmlElement;
		}

		public void getState(XmlElement clientState, string pageHtml)
		{
			bool changedTemplate = false;
			clientState.SetAttribute("fields", m_processedFields);

			if (pageHtml != null && m_newTemplateNames != null && string.Compare(m_newTemplateNames, m_oldTemplateNames) != 0)
			{
				StringBuilder scriptBuff = new StringBuilder(/*generatePushPullClientScriptMethods(true)*/);
				changedTemplate = true;
				Regex r = new Regex(string.Format("<!--{0}-->(.*?)<!--{0}-->", ClientID.Replace("$","\\$")), RegexOptions.IgnoreCase | RegexOptions.Singleline);
				Match m = r.Match(pageHtml);
				if (m.Success)
				{
					StringBuilder htmlBuff = new StringBuilder();
					string myHtml = m.Groups[1].Value;
					r = new Regex("<script.*?>(.*?)</script>", RegexOptions.IgnoreCase | RegexOptions.Singleline);
					MatchCollection mc = r.Matches(myHtml);
					int lastPos = 0;
					foreach (Match m2 in mc)
					{
						htmlBuff.Append(myHtml.Substring(lastPos, m2.Index - lastPos));
						//if (!m2.Groups[1].Value.StartsWith("//" + ClientID + ":"))
							scriptBuff.Append(m2.Groups[1].Value);
						lastPos = m2.Index + m2.Length;
					}
					//if (lastPos != 0)
					htmlBuff.Append(myHtml.Substring(lastPos));

					clientState.SetAttribute("html", htmlBuff.ToString());
				}
				StringBuilder buff = new StringBuilder();
				searchForCtlSetup(this, buff, pageHtml, "");
				scriptBuff.Append(buff);
				clientState.SetAttribute("script", scriptBuff.ToString());
			}

			XmlElement bizobj = clientState.OwnerDocument.CreateElement("BizObj");
			clientState.AppendChild(bizobj);

			string controlScript = null;
			setupClientBizObjXml(this, bizobj, ref controlScript);
		}

		protected void setupClientBizObjXml(Control parent, XmlElement bizobj, ref string controlScript)
		{
			//Controls that have id's that start with _ are not data fields and are exposed to having their
			//data localized through the static content localization mechanism.  Only have a control field
			//start with _ if it is for static content.
			foreach (Control ctl in parent.Controls)
				if (ctl.ID != null)
					if (ctl.ID[0] != '_')
					{
						if (s_controlPushScripts.TryGetValue(ctl.GetType().Name.ToLower(), out controlScript))
						{
							string name = getAttrName(ctl.ID);
							object val = getControlValueForBizObjField(ctl, CurrentBizObj == null ? null : (BizObjField)CurrentBizObj.type.getField(name, false), CurrentBizObj, name, false);
							bizobj.SetAttribute(name, val == null ? "" : val.ToString());
							/*
							if (CurrentBizObj != null && CurrentBizObj.containsField(name))
							{
								object val = CurrentBizObj.get(name);
								bizobj.SetAttribute(name, val == null ? "" : val.ToString());
							}
							*/
						}
						else
							setupClientBizObjXml(ctl, bizobj, ref controlScript);
					}
					else if(ctl is HyperLink)
					{
						if (s_controlPushScripts.TryGetValue(ctl.GetType().Name.ToLower(), out controlScript))
						{
							string name = getAttrName(ctl.ID);
							object val = getControlValueForBizObjField(ctl, null, CurrentBizObj, name, false);
							bizobj.SetAttribute(name, val == null ? "" : val.ToString());
						}
					}
		}

		protected void searchForCtlSetup(Control parentCtl, StringBuilder buff, string pageHtml, string parentCtlIdPrefix)
		{
			foreach (Control ctl in parentCtl.Controls)
				if(ctl.ID != null)
				{
					NitobiComboBoxBizObjListDisplay combo = ctl as NitobiComboBoxBizObjListDisplay;
					if (combo != null)
						buff.AppendFormat(s_nitobiComboSetupScript, "", ctl.ClientID, combo.LiveScroll.ToString().ToLower());
					if (ctl.GetType().Name.ToLower() == "freetextbox")
					{
						Regex rr = new Regex(@"if\s*\(window\.FTB_AddEvent\).*?(FTB_API\['" + ctl.ClientID + @"'\].*?)\}\)\;.*?//FTB_API", RegexOptions.Singleline);
						Match mm = rr.Match(pageHtml);
						if (mm.Success)
							buff.Append(mm.Groups[1].Value);
					}
					else
						searchForCtlSetup(ctl, buff, pageHtml, parentCtl.ClientID.Replace('$', '_') + "_");
				}
		}

		protected override object getControlValueForBizObjField(Control ctl, BizObjField fldDef, IBizObj obj, string fullFieldName, bool convertType)
		{
			if (ctl is NitobiComboBoxBizObjListDisplay)
			{
				if (fullFieldName == null || obj == null)
					return null;
				else
				{
					IBizObj sel = ((NitobiComboBoxBizObjListDisplay)ctl).SelectedBizObj;
					object ctlValue = null;
					if (sel != null)
						ctlValue = sel.KeyValue;
					if (ctlValue == null)
					{
						ctlValue = Page.Request.Form[ctl.ClientID + "_cmSelectedValue0"];
						if (Swaf.Cmn.IsEmpty(ctlValue))
							ctlValue = obj.get(fullFieldName);
					}

					if (convertType && ctlValue != null && fldDef != null)
						ctlValue = createPlatformTypeInstance(fldDef, ctlValue);
					return ctlValue;
				}
			}
			return base.getControlValueForBizObjField(ctl, fldDef, obj, fullFieldName, convertType);
		}
		/*
		public void processClientBizObj(XmlElement bizobj, Swaf.Gui.IBizProcess bizProc)
		{
		}

		public void getClientBizObj(XmlElement bizobj, Swaf.Gui.IBizProcess bizProc)
		{
		}
		*/
		public void processAjaxEvent(XmlElement eventInfo)
		{
		}

		public override string WidgetId
		{
			get { return ClientID; }
		}

		#endregion

		protected override bool processControl(Control ctl, string fieldName)
		{
			if (ctl is NitobiComboBoxBizObjListDisplay)
			{
				NitobiComboBoxBizObjListDisplay nc = (NitobiComboBoxBizObjListDisplay)ctl;
				WidgetOfflineState ws = BPC.getWidgetOfflineState(nc.WidgetId);
				if (ws == null)
					ws = BPC.addWidgetOfflineState(new WidgetOfflineState(nc.WidgetId, nc.GetType().AssemblyQualifiedName));
				ws.State.put("ParentControlBizProcIds", State.ParentBizProcIds[0]);
			}
			return base.processControl(ctl, fieldName);
		}
	}
}
